/**
 * Copyright 2000-2011 Worth Enterprises, Inc. All rights reserved.
 */
package com.worthent.foundation.util.state.provider;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.worthent.foundation.util.state.StateEvent;
import com.worthent.foundation.util.state.StateTable;
import com.worthent.foundation.util.state.StateTableData;
import com.worthent.foundation.util.state.TransitionActor;
import com.worthent.foundation.util.state.annotation.Actor;
import com.worthent.foundation.util.state.annotation.MethodActor;

/**
 * Scan the methods in a specified class for actor annotations and constructs
 * actors for each one it finds.
 * 
 * @author Erik K. Worth
 * @version $Id: AnnotatedActorBuilder.java 2 2011-11-28 00:10:06Z erik.k.worth@gmail.com $
 * 
 */
public class AnnotatedActorBuilder {

    /** Logger for this class */
    private static final Logger LOGGER =
        LoggerFactory.getLogger(AnnotatedActorBuilder.class);

    /** The actors keyed by the actor name */
    private final Map<String, TransitionActor> actors;

    /** Construct with a map of actors to augment */
    AnnotatedActorBuilder(final Map<String, TransitionActor> actors) {
        this.actors = actors;
    }

    /** Returns the actors accumulated from annotated methods */
    Map<String, TransitionActor> getActors() {
        return actors;
    }

    /**
     * Adds actors to the state table for each annotated method.
     * 
     * @param annotatedClass the class to introspect for Actor annotations
     * @return this builder
     */
    AnnotatedActorBuilder addActors(final Class<?> annotatedClass) {
        final String className = annotatedClass.getName();
        final Method[] methods = annotatedClass.getMethods();
        for (final Method method : methods) {
            final Actor actor = method.getAnnotation(Actor.class);
            String name = actor.name();
            if (null == name) {
                name = method.getName();
            }
            if (hasAppropriateSignature(className, name, method)) {
                final int modifiers = method.getModifiers();
                if (Modifier.isStatic(modifiers)) {
                    final MethodActor methodActor =
                        new MethodActor(method, name);
                    actors.put(name, methodActor);
                } else {
                    if (StateTableData.class.isAssignableFrom(annotatedClass)) {
                        final MethodActor methodActor =
                            new MethodActor(annotatedClass, method, name);
                        actors.put(name, methodActor);
                    } else {
                        LOGGER.warn("Ignoring actor, '" + name +
                            "', in class '" + className +
                            "', because the annotated method is not not " +
                            "static and the class does not extend " +
                            StateTableData.class.getName());
                    }
                }
            }
        }
        return this;
    }

    /**
     * Returns <code>true</code> when the annotated method has the appropriate
     * method signature.
     * 
     * @param className the name of the annotated class
     * @param actorName the name of the method actor
     * @param method the method with the Actor annotation
     * @return <code>true</code> when the annotated method has the appropriate
     *         method signature
     */
    private static boolean hasAppropriateSignature(
        final String className,
        final String actorName,
        final Method method) {
        final int modifiers = method.getModifiers();
        final Class<?>[] types = method.getParameterTypes();
        if (!Modifier.isPublic(modifiers)) {
            LOGGER.warn("Ignoring actor, '" + actorName + "', in class '" +
                className, "', because the annotated method is not public.");
            return false;
        }
        if (types.length != 4) {
            LOGGER.warn("Ignoring actor, '" + actorName + "', in class '" +
                className, "', because the method does not have 4 parameters.");
            return false;
        }
        if (!String.class.isAssignableFrom(types[0])) {
            LOGGER.warn(
                "Ignoring actor, '" + actorName + "', in class '" + className,
                "', because the first method parameter is not a String.");
            return false;
        }
        if (!String.class.isAssignableFrom(types[1])) {
            LOGGER.warn(
                "Ignoring actor, '" + actorName + "', in class '" + className,
                "', because the second method parameter is not a String.");
            return false;
        }
        if (!StateTable.class.isAssignableFrom(types[2])) {
            LOGGER.warn(
                "Ignoring actor, '" + actorName + "', in class '" + className,
                "', because the third method parameter is not a StateTable.");
            return false;
        }
        if (!StateEvent.class.isAssignableFrom(types[3])) {
            LOGGER.warn(
                "Ignoring actor, '" + actorName + "', in class '" + className,
                "', because the forth method parameter is not a StateEvent.");
            return false;
        }
        // This method has the right signature
        return true;
    }
}
